package com.miyake.dsp.osa.lab;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang.ArrayUtils;

import com.miyake.dsp.common.CommonTool;
import com.miyake.dsp.common.EdgeDetector;
import com.miyake.dsp.common.EdgeDetector.Edge;

public class NoiseDetector {
	public double getThreshold() {
		return threshold;
	}

	public double[] getDiffTrace() {
		return diffTrace;
	}

	public double[] getSortedY() {
		return sortedy;
	}

	public double[] getSortedX() {
		return sortedx;
	}
	
	public double[] getXout() {
		return xout;
	}

	public double[] getYout() {
		return yout;
	}

	public double getNoiseFloor() {
		return noiseFloor;
	}

	public double[] getNoiseStart() {
		return noiseStart;
	}
	
	public double[] getSignalStart() {
		return signalStart;
	}
	
	private double[] sortedy;
	private double[] sortedx;
	private double[] xout;
	private double[] yout;
	private double noiseFloor;
	private double[] noiseStart;
	private double[] signalStart;
	private double threshold;
	private double[] diffTrace;
	
	public void setData(double[] y) throws EdgeNotFoundException {
		setData(null, y);
	}
	
	public void setData(double[] x, double[] y) throws EdgeNotFoundException {
		// Sorts data
		if (x == null) {
			x = new double[y.length];
			for (int i = 0; i < y.length; i++) {
				x[i] = i;
			}
		}
		Sort sort = new Sort(x, y);
		double[] xx = sort.getOutx();
		double[] yy = sort.getOuty();
		int[] ii = sort.getOutXindex();
		sortedy = yy;
		sortedx = xx;
		
		int noiseIndex = 0;
		
		EdgeDetector edge = new EdgeDetector(Edge.Rise, xx, yy);
		edge.process();
		int[] indexes = edge.getIndexes();
		double[] diffs = edge.getDiffValues();
		diffTrace = edge.getDiff();
		threshold = edge.getThresholds().get(0);
		
		if (indexes.length == 0) {
			throw new EdgeNotFoundException();
		}
		noiseIndex = indexes[0];

		for (int i = 0; i < diffs.length; i++) {
			if (indexes[i] >= y.length/20) {
				noiseIndex = indexes[i];
				xout = ArrayUtils.subarray(xx, 0, noiseIndex);
				break;
			}
		}
		

		noiseFloor = yy[noiseIndex];
		
		//noiseStart = getNoiseStart(x, y, noiseFloor);
		searchNoiseStart2(x, y, xx, yy, ii, noiseIndex);
		
		yout = edge.getY();
		
		int[] subArray = ArrayUtils.subarray(ii, 0, noiseIndex);
		int[] newArray = Arrays.copyOf(subArray, subArray.length);
		Arrays.sort(newArray);
		List<Integer> sepList = new ArrayList<Integer>();
		int start = newArray[0];
		//sepList.add(start);
		for (int i = 1; i < newArray.length; i++) {
			if (newArray[i] - newArray[i-1] >= 3) {
				sepList.add(newArray[i-1]);
				sepList.add(newArray[i]);
			}
		}
		
		double[] sep = new double[sepList.size()];
		int i = 0;
		for (Integer index : sepList) {
			sep[i++] = x[index];
		}
	}

	private void searchNoiseStart2(final double[] x, double[] y, double[] xx,
			final double[] yy, int[] ii, final int noiseIndex) {

		int[] signalIndexes = ArrayUtils.subarray(ii, noiseIndex+1, ii.length);
		double noiseLevel = yy[noiseIndex];
		final double snr = Math.log10(CommonTool.getMaxValue(y) / noiseLevel);
		Arrays.sort(signalIndexes);
		
		final List<Double> signalStartList = new ArrayList<Double>();
		final List<Double> noiseStartList = new ArrayList<Double>();
		
		abstract class SearchSeparation {
			abstract public void result(int start, int end, double max);
			public SearchSeparation(int index, double[] y, double noiseLevel) {
//				double peak = CommonTool.getMaxValue(y);
				int start = 0;
				int end = 0;
				double max = Double.MIN_VALUE;
				for (int i = index; i < y.length; i++) {
					if (max < y[i]) {
						max = y[i];
					}
					if (y[i] < noiseLevel) {
						end = i;
						break;
					}
				}
				for (int i = index; i >= 0; i--) {
					if (max < y[i]) {
						max = y[i];
					}
					if (y[i] < noiseLevel) {
						start = i;
						break;
					}
				}
				result(start, end, max);
			}
		};
		final int threshold = 3;
		for (int i = 0; i < signalIndexes.length - 1; i++) {
			if (signalIndexes[i+1] - signalIndexes[i] > 1 || i == signalIndexes.length-2) {
				new SearchSeparation(signalIndexes[i], y, yy[noiseIndex]) {
					@Override
					public void result(int start, int end, double max) {
						if (max > yy[noiseIndex]*snr) {
							signalStartList.add(x[start]);
							noiseStartList.add(x[end]);
						}
						else {
							//System.out.println("max = "+max + ",noise level=" + yy[noiseIndex]*2);
						}
					}
				};
			}
		}
		this.noiseStart = ArrayUtils.toPrimitive(noiseStartList.toArray(new Double[0]));
		this.signalStart = ArrayUtils.toPrimitive(signalStartList.toArray(new Double[0]));
	}

	private Integer searchRight(int index, double[] y, double noiseLevel) {
		for (int i = index; i < y.length; i++) {
			if (y[i] < noiseLevel) {
				return i;
			}
		}
		return null;
	}

	private Integer searchLeft(int index, double[] y, double noiseLevel) {
		for (int i = index; i >= 0; i--) {
			if (y[i] < noiseLevel) {
				return i;
			}
		}

		return null;
	}

	private double[] getNoiseStart(double[] x, double[] y, double noiseFloor2) {
		List<Double> list = new ArrayList<Double>();
		int threshold = 10;
		
		int currentNoise = 0;
		int noiseStart = 0;
		int currentSignal = 0;
		int signalStart = 0;
		boolean noiseArea = false;
		for (int i = 0; i < y.length; i++) {
			if (y[i] <= noiseFloor2) {
				if (!noiseArea) {
					noiseStart = i;
					noiseArea = true;
				}
				currentNoise = i;
			
			}
			else {
				if (noiseArea) {
					if (i - currentNoise > threshold) {
						noiseArea = false;
						list.add(x[noiseStart]);
						list.add(x[currentSignal]);
					}
					currentSignal = i;
				}
				
			}
		}
		if (noiseArea) {
			list.add(x[noiseStart]);
		}
		return ArrayUtils.toPrimitive(list.toArray(new Double[0]));
	}
}
